package com.lc.midi.keyEvent;


import com.lc.midi.mid.*;
import com.lc.midi.script.Config;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class KeyEventsFactory {

	public KeyEvents createDefaultKeyEvents(String filePath) throws NumberFormatException, MidiException, IOException {
		Midi midi = new Midi(new MidiFile(filePath));
		return createDefaultKeyEvents(midi);
	}

	public KeyEvents createDefaultKeyEvents(Midi midi) throws NumberFormatException, MidiException {
		KeyEvents events;
		if (midi.getHead().getMtrkType() == MidiType.multiMtrkSync) {
			events = createSyncKeyEvents(midi);
		} else {
			events = createNotSyncKeyEvents(midi);
		}
		removeBeginBlank(events);
		return events;
	}
	
//	public KeyEvents createDefaultKeyEvents(entity.Midi midi) throws IOException, MidiException {
//		Midi midi2 = new Midi(new MidiFile(midi));
//		return createDefaultKeyEvents(midi2);
//	}
//
	public KeyEvents createKeyEvents(String filePath, Config config) throws IOException, MidiException {
		Midi midi = new Midi(new MidiFile(filePath));
		return createKeyEvents(midi, config);
	}
	
	public KeyEvents createKeyEvents(Midi midi, Config config) throws NumberFormatException, MidiException {
		KeyEvents events;
		events = createDefaultKeyEvents(midi);
		int maxBlank = config.getMaxBlankTime();
		removeBlank(events, maxBlank);
		return events;
	}
	
//	public KeyEvents createKeyEvents(entity.Midi midie, Config config) throws IOException, MidiException {
//		Midi midi2 = new Midi(new MidiFile(midie));
//		return createKeyEvents(midi2, config);
//	}
//

	private KeyEvents createSyncKeyEvents(Midi midi) throws NumberFormatException, MidiException {
		Map<Integer, Integer> changeSpeed = new HashMap<Integer, Integer>();
		Map<Integer, Integer> changeInstrument = new HashMap<Integer, Integer>();
		int[] instruments = new int[16];
		for (int i = 0; i < instruments.length; i++) {
			instruments[i] = 0; 
		}
		int now = 0;
		int ticks = midi.getHead().getTicks();
		KeyEvents keyEvents = new KeyEvents();
		for (int i = 0; i < midi.getTracks().size(); i++) {
			MidiTrack metaTrack = midi.getTracks().get(i);
			for (MidiEvent event : metaTrack.getMidiEvents()) {
				now += event.getDeltaTime();
				// change speed
				if (event.getEventType() == MidiEventType.metaEvent) {
					if (event.getMessage().containsKey(MidiEventMsg.speed)) {
						changeSpeed.put(now, Integer.parseInt(event.getMessage().get(MidiEventMsg.speed)));
					}
				} else if (event.getEventType() == MidiEventType.changeInstrument) {
					changeInstrument.put(now, Integer.parseInt(event.getMessage().get(MidiEventMsg.instrumentCode)));

				}
			}
		}
		for (int i = 0; i < midi.getTracks().size(); i++) {
			int speed = changeSpeed.get(0) == null ? 0 : changeSpeed.get(0);
			int instrument = changeInstrument.get(0) == null ? 0 : changeInstrument.get(0);
			now = 0;
			int nowRealTime = 0;
			for (var event : midi.getTracks().get(i).getMidiEvents()) {
				now += event.getDeltaTime();
				nowRealTime += getRealTime(event.getDeltaTime(), speed, ticks);
				if (changeSpeed.containsKey(now)) {
					speed = changeSpeed.get(now);
				}
				if (changeInstrument.containsKey(now)) {
					instrument = changeInstrument.get(now);
				}
				if (event.getEventType() == MidiEventType.noteOn) {
					keyEvents.addKeyEvent(nowRealTime, Integer.parseInt(event.getMessage().get(MidiEventMsg.keyCode)),
							instrument, Integer.parseInt(event.getMessage().get(MidiEventMsg.pass)));
				}
				if (event.getEventType() == MidiEventType.changeInstrument) {
					instrument = Integer.parseInt(event.getMessage().get(MidiEventMsg.instrumentCode));
				}
				if (event.getEventType() == MidiEventType.metaEvent) {
					if (event.getMessage().containsKey(MidiEventMsg.speed)) {
						speed = Integer.parseInt(event.getMessage().get(MidiEventMsg.speed));
					}
				}
			}
		}
		return keyEvents;
	}

	private KeyEvents createNotSyncKeyEvents(Midi midi) throws NumberFormatException, MidiException {
		KeyEvents keyEvents = new KeyEvents();
		int ticks = midi.getHead().getTicks();
		for (MidiTrack track : midi.getTracks()) {
			int speed = 0;
			int instrument = 0;
			int nowRealTime = 0;
			for (MidiEvent event : track.getMidiEvents()) {
				nowRealTime += getRealTime(event.getDeltaTime(), speed, ticks);
				if (event.getEventType() == MidiEventType.changeInstrument) {
					instrument = Integer.parseInt(event.getMessage().get(MidiEventMsg.instrumentCode));
				}
				if (event.getEventType() == MidiEventType.metaEvent) {
					if (event.getMessage().containsKey(MidiEventMsg.speed)) {
						speed = Integer.parseInt(event.getMessage().get(MidiEventMsg.speed));
					}
				}
				if (event.getEventType() == MidiEventType.noteOn) {
					keyEvents.addKeyEvent(nowRealTime, Integer.parseInt(event.getMessage().get(MidiEventMsg.keyCode)),
							instrument, Integer.parseInt(event.getMessage().get(MidiEventMsg.pass)));
				}
			}
		}
		return keyEvents;
	}

	private int getRealTime(int deltaTime, int speed, int ticks) {
		if (speed == 0) {
			speed = 0x0927c0;
		}
		return (int) ((long) deltaTime * speed / ticks / 1000);
	}
	
	
	private void removeBeginBlank(KeyEvents keyEvents) {
		int t = keyEvents.getKeyEventList().get(0).getTime();
		if (t > 0) {
			List<KeyEvent> kList = keyEvents.getKeyEventList();
			for (int i = 0; i < kList.size(); i++) {
				kList.get(i).setTime(kList.get(i).getTime() - t);
			}
		}
	}
	
	private void removeBlank(KeyEvents keyEvents, int maxBlank) {
		List<KeyEvent> kList = keyEvents.getKeyEventList();
		int t = kList.get(0).getTime();
		int oldTime = 0;
		for (int i = 0; i < kList.size(); i++) {
			kList.get(i).setTime(kList.get(i).getTime() - t);
			if (kList.get(i).getTime() - oldTime > maxBlank) {
				t += kList.get(i).getTime() - oldTime - maxBlank;
				kList.get(i).setTime(oldTime + maxBlank);
			}
			oldTime = kList.get(i).getTime();
		}
	}
}
